package org.durandj.ystream.server;

import java.io.IOException;

import org.durandj.ystream.server.message.Message;
import org.durandj.ystream.server.message.MessageHandler;
import org.durandj.ystream.server.network.NetworkDaemon;
import org.durandj.ystream.server.playlist.Playlist;
import org.durandj.ystream.server.playlist.PlaylistAdapter;
import org.durandj.ystream.server.playlist.PlaylistListener;
import org.durandj.ystream.server.ui.ConsoleUI;
import org.durandj.ystream.server.ui.ServerGUI;
import org.durandj.ystream.server.ui.ServerUI;
import org.durandj.ystream.server.util.Settings;
import org.durandj.ystream.server.util.Timer;

/**
 * The main class of the YStream server. This is where the application gets started and this is the class
 * that handles the messaging and such of the system.
 * 
 * @author durandj
 * @version 1.0
 *
 */
public class Server extends MessageHandler
{
	/**
	 * The starting point of the server application. This creates an instance of the <code>Server</code> class
	 * and uses that to run the program. Arguments can be specified to the server to control various aspects of it.
	 * To get a list of the commands use this command: java -jar YStreamServer.jar --help
	 * TODO: Review when the jar is created.
	 * 
	 * @param args - command line arguments to the server
	 */
	public static void main(String[] args)
	{
		Class<?> uiType = null;
		
		// Check if there were any arguments specified.
		if(args.length > 0)
		{
			// Check for the help parameter.
			if(args[0].equalsIgnoreCase("--help"))
			{
				System.out.println("Usage: java -jar YStreamServer.jar [PARAMETERS]");
				System.out.println("PARAMETERS:\n");
				System.out.println("--help\tDisplays this message.");
				System.out.println("--nogui\tOpens the server with no GUI.");
				System.out.println("--gui\tOpens the server with the GUI.");
				System.out.println("\nBy default the --gui parameter is specified unless otherwise stated.");
				System.exit(0);
			}
			else if(args[0].equalsIgnoreCase("--nogui"))
			{
				uiType = ConsoleUI.class;
			}
			else if(args[0].equalsIgnoreCase("--gui"))
			{
				uiType = ServerGUI.class;
			}
		}
		
		singleton = new Server(uiType);
		singleton.run();
	}
	
	/**
	 * There should only ever be one server instance so lets make it easy to access.
	 */
	private static Server singleton;
	
	/**
	 * Gets a reference to the server.
	 * 
	 * @return server reference
	 */
	public static Server getServer()
	{
		return singleton;
	}
	
	/**
	 * The server version.
	 */
	public static final String VERSION = "0.0.1";
	
	/**
	 * The flag for telling the server it's alive or not.
	 */
	private boolean isAlive;
	
	/**
	 * The network monitor daemon.
	 */
	private NetworkDaemon networkDaemon;
	
	/**
	 * The client manager.
	 */
	private ClientManager clientManager;
	
	/**
	 * The designated server UI.
	 */
	private ServerUI ui;
	
	/**
	 * The server's playlist.
	 */
	private Playlist playlist;
	
	/**
	 * Listens for changes in the playlist.
	 */
	private PlaylistListener playlistListener;
	
	/**
	 * Creates the server.
	 * 
	 * @param uiType - The type of UI to use.
	 */
	private Server(Class<?> uiType)
	{
		log.info("Starting YStream server...");
		
		if(uiType == null)
			uiType = ServerGUI.class;
		log.info("Using the " + uiType + " UI for the server.");
		
		try
		{
			log.info("Initializing server UI...");
			ui = (ServerUI)Class.forName(uiType.getName()).newInstance();
			log.info("Server UI started!");
		}
		catch(Exception e)
		{
			log.warning("Unable to start server UI!");
			System.exit(1);
		}
		
		log.info("Creating playlist...");
		playlist = new Playlist();
		log.info("Playlist created!");
		
		log.info("Listening for playlist changes...");
		playlistListener = new PlaylistAdapter() {
			// TODO:
		};
		playlist.addListener(playlistListener);
		
		log.info("Verify server settings...");
		verifySettings();
		log.info("Server settings verified!");
		
		log.info("Starting network daemon...");
		try
		{
			networkDaemon = new NetworkDaemon(Integer.parseInt(Settings.getSettings().getSetting("server_port")));
		}
		catch(IOException e)
		{
			log.warning("Could not make connection to network socket on port " +
			            Settings.getSettings().getSetting("server_port") + "\n" +
			            e.getMessage());
			System.exit(1);
		}
		log.info("Network daemon started!");
		
		log.info("Starting client manager...");
		clientManager = new ClientManager(Integer.parseInt(Settings.getSettings().getSetting("max_connections")));
		ui.setClientManager(clientManager);
		log.info("Client manager started!");
		
		isAlive = true;
	}
	
	/**
	 * This is where messaging is handled.
	 */
	public void run()
	{
		while(isAlive)
		{
			if(noMessages())
				waitForMessage();
			
			Message msg = nextMessage();
			handleMessage(msg);
		}
	}
	
	/**
	 * Checks the servers settings and if necessary sets defaults for it.
	 */
	private void verifySettings()
	{
		log.info("Checking max number of connections...");
		String setting = Settings.getSettings().getSetting("max_connections");
		if(setting == null || setting.isEmpty())
		{
			log.info("No setting for maximum connections. Using default value.");
			Settings.getSettings().addSetting("max_connections", "20");
		}
		else
		{
			int val = 0;
			try
			{
				val = Integer.parseInt(setting);
			}
			catch(NumberFormatException e)
			{
				log.info("Malformed setting for max connections. Using default value.");
				Settings.getSettings().setSetting("max_connections", "20");
			}
			
			if(val <= 0)
			{
				log.info("Invalid setting for max connections. Using default value.");
				Settings.getSettings().setSetting("max_connections", "20");
			}
		}
		
		// TODO: Use the value for the connections.
		
		log.info("Checking max number of items in playlist...");
		setting = Settings.getSettings().getSetting("max_playlist");
		if(setting == null || setting.isEmpty())
		{
			log.info("No setting for maximum playlist size.");
			Settings.getSettings().addSetting("max_playlist", "50");
		}
		else
		{
			int val = 0;
			try
			{
				val = Integer.parseInt(setting);
			}
			catch(NumberFormatException e)
			{
				log.info("Malformed setting for max playlist size. Using default value.");
				Settings.getSettings().setSetting("max_playlist", "50");
			}
			
			if(val <= 0)
			{
				log.info("Invalid setting for max playlist connections. Using default value.");
				Settings.getSettings().setSetting("max_playlist", "50");
			}
		}
		
		playlist.setMaxElements(Integer.parseInt(Settings.getSettings().getSetting("max_playlist")));
		
		log.info("Checking number of files to transfer at a time...");
		setting = Settings.getSettings().getSetting("file_transfer");
		if(setting == null || setting.isEmpty())
		{
			log.info("No setting for number of files to transfer. Using default value.");
			Settings.getSettings().addSetting("file_transfer", "10");
		}
		else
		{
			int val = 0;
			try
			{
				val = Integer.parseInt(setting);
			}
			catch(NumberFormatException e)
			{
				log.info("Malformed setting for number of files to transfer. Using default value.");
				Settings.getSettings().setSetting("file_transfer", "10");
			}
			
			if(val <= 0)
			{
				log.info("Invalid setting for number of files to transfer. Using default value.");
				Settings.getSettings().setSetting("file_transfer", "10");
			}
		}
		
		// TODO: Use the value that was found!
		
		log.info("Checking value for allowing client submittions...");
		setting = Settings.getSettings().getSetting("allow_submit");
		if(setting == null || setting.isEmpty())
		{
			log.info("No setting for allowing client submittions. Using default value.");
			Settings.getSettings().addSetting("allow_submit", "true");
		}
		else if(!setting.equalsIgnoreCase("true") && !setting.equalsIgnoreCase("false"))
		{
			log.info("Malformed setting for allowing client submittions. Using default value.");
			Settings.getSettings().setSetting("allow_submit", "true");
		}
		
		// TODO: Use the value that was found!
		
		log.info("Checking value for server port...");
		setting = Settings.getSettings().getSetting("server_port");
		if(setting == null || setting.isEmpty())
		{
			log.info("No setting for the server port. Using default value.");
			Settings.getSettings().addSetting("server_port", "12345");
		}
		else
		{
			int val = 0;
			try
			{
				val = Integer.parseInt(setting);
			}
			catch(NumberFormatException e)
			{
				log.info("Malformed setting for server port. Using default value.");
				Settings.getSettings().setSetting("server_port", "12345");
			}
			
			if(val <= 0)
			{
				log.info("Invalid setting for server port. Using default value.");
				Settings.getSettings().setSetting("server_port", "12345");
			}
		}
		
		// TODO: Use the value that was found!
	}
	
	/**
	 * Gets the server's playlist.
	 * 
	 * @return the playlist
	 */
	public Playlist getPlaylist()
	{
		return playlist;
	}
	
	/**
	 * Gets a reference to the server's client manager.
	 * 
	 * @return the client manager
	 */
	public ClientManager getClientManager()
	{
		return clientManager;
	}
	
	/**
	 * Gets a reference to the server's network daemon.
	 * 
	 * @return the network daemon
	 */
	public NetworkDaemon getNetworkDaemon()
	{
		return networkDaemon;
	}

	/*
	 * (non-Javadoc)
	 * @see org.durandj.ystream.server.message.MessageHandler#handleMessage(org.durandj.ystream.server.message.Message)
	 */
	@Override
	protected void handleMessage(Message msg)
	{
		switch(msg.getID())
		{
		case KILL:
			isAlive = false;
			
			// Lets clean up the UI.
			ui.destroy();
			
			networkDaemon.queueMessage(msg);
			
			while(networkDaemon.isAlive())
				new Timer(100);
			
			clientManager.queueMessage(msg);
			
			// TODO: We may want to make it so that the various threads can be joined together to close the application.
			break;
		
		default:
			log.warning("Unhandled message!\n" +
						"\tID: " + msg.getID() + "\n" +
						"\tSender: " + msg.getSender().getClass().getSimpleName() + "\n");
			break;
		}
	}
}
